package implementation1.app;

import exceptions.StreamException;
import implementation1.app.streams.FileStream;
import interfaces.app.IAnalyzer;
import interfaces.app.IStream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

/**
 * Created: 31.10.11
 *
 * @author Jenda Kolena, jendakolena@gmail.com
 * @version 0.1
 */
public class Analyzer implements IAnalyzer
{
    public static final String PIPE = "pipe";

    private String command;
    private String commandName;
    private ArrayList<String> commandArgs;
    private HashMap<String, String> forwarding;
    private String basePath;

    /**
     * Set command for analyzing.
     *
     * @param command the command
     */
    public void setCommand(String command)
    {
        command = command.trim();
        this.command = command;
        commandArgs = new ArrayList<String>();
        forwarding = new HashMap<String, String>();

        if (command.contains("|"))
        {//the pipe
            commandName = PIPE;
            String[] parts = command.split("\\|", 2);

            for (String part : parts)
            {
                commandArgs.add(part.trim());
            }
            return;
        }

        //other than "pipe"
        String[] parts = command.split(" ", 2);
        commandName = parseCommandName(parts[0]);

        //if the command has any args
        if (parts.length == 2) parseArguments(parts[1]);

        //if the command has any IO forward
        if (Math.max(command.indexOf("<"), command.indexOf(">")) != -1) parseIOForwarding(command);
    }

    public void setBasePath(String path)
    {
        basePath = path;
    }

    private String parseCommandName(String command)
    {
        return command.replaceAll("(.*)[<> ](.*)", "$1");
    }

    /**
     * Finds end of forwarding part of the command.
     *
     * @param command The whole command.
     * @param pattern The prefix of forwarding we're looking for.
     * @param pos     Position of pattern start.
     * @return End of forwarding argument.
     */
    private int recognizeEnd(String command, String pattern, int pos)
    {
        int[] ends = new int[3];
        int end = Integer.MAX_VALUE;

        //fill array with default values
        Arrays.fill(ends, Integer.MAX_VALUE);
        ends[0] = command.indexOf(" ", pos + pattern.length() + 1);
        ends[1] = command.indexOf(">", pos + pattern.length() + 1);
        ends[2] = command.indexOf("<", pos + pattern.length());

        //recognize best value
        for (int i = 0; i < ends.length; i++)
        {
            if (ends[i] < end && ends[i] >= 0) end = ends[i];
        }

        //if none of wanted chars were found...
        if (end == -1 || end == Integer.MAX_VALUE) end = command.length();

        return end;
    }

    /**
     * Finds forwarding declarations in the command args.
     *
     * @param command The whole command.
     */
    private void parseIOForwarding(String command)
    {
        String pattern;
        int pos;
        String name;

        //delete all arguments containing < or >
        for (String arg : commandArgs)
        {
            if (arg.matches("(.*)([<>])(.*)")) command = command.replaceAll("\"" + arg + "\"", "§arg§");
        }

        pattern = "<";
        if ((pos = command.lastIndexOf(pattern)) != -1)
        {
            name = command.substring(pos + pattern.length(), recognizeEnd(command, pattern, pos));
            forwarding.put("input", name.trim());
        }

        pattern = ">";
        if ((pos = command.lastIndexOf(pattern)) != -1)
        {
            try
            {
                while ((pos >= 3 && command.substring(pos - 2, pos).compareTo(" 2") == 0))
                {//found last occurence of ">", but it was "2>"
                    if ((pos = command.substring(0, pos).lastIndexOf(pattern)) == -1)
                    {
                        //if no other ">" (than "2>") were found
                        throw new IllegalStateException();
                    }
                }

                name = command.substring(pos + pattern.length(), recognizeEnd(command, pattern, pos));
                forwarding.put("output", name.trim());
            } catch (IllegalStateException e)
            {
                //it should be empty!
            }
        }

        pattern = " 2>";
        if ((pos = command.lastIndexOf(pattern)) != -1)
        {
            name = command.substring(pos + pattern.length(), recognizeEnd(command, pattern, pos));
            forwarding.put("error", name.trim());
        }
    }


    /**
     * Parses commands arguments.
     *
     * @param args Part of command which contains arguments.
     */
    private void parseArguments(String args)
    {
        boolean inQuote = false;
        boolean afterSign = false;

        //insert space between argument and next forwarding declaration
        args = args.replaceAll("([a-zA-Z_])([<>])", "$1 $2");

        for (int index = 0; index < args.length(); index++)
        {
            switch (args.charAt(index))
            {
                case '"':
                    inQuote = !inQuote;
                    break;
                case '>':
                case '<':
                    afterSign = true;
                    break;
                case ' ':
                {
                    if (!inQuote)
                    {
                        args = args.substring(0, index) + (afterSign ? "" : "\n") + args.substring(index + 1);
                    }
                }
                break;
                default:
                {
                    afterSign = false;
                }
                break;
            }
        }

        String[] parts = args.replaceAll("\"", "").split("\n");

        for (String part : parts)
        {
            if (isValidArgument(part)) commandArgs.add(part.trim());
        }

    }

    /**
     * Determines whether the argument is valid (and it's NOT a forwarding declaration).
     *
     * @param arg The argument.
     * @return TRUE if the argument is valid.
     */
    private boolean isValidArgument(String arg)
    {
        if (arg.length() == 0) return false;

        String[] forwardPrefixes = {"<", ">", "2>", "1>"};

        for (String prefix : forwardPrefixes)
        {
            //after the < or > sign it can be various amount of spaces
            if (arg.trim().matches("^" + prefix + "( )*(.*)")) return false;
        }

        return true;
    }

    /**
     * Gets input name.
     *
     * @return Input name.
     */
    public String getInputName()
    {
        return forwarding.get("input");
    }

    /**
     * Gets output name.
     *
     * @return Output name.
     */
    public String getOutputName()
    {
        return forwarding.get("output");
    }

    /**
     * Gets error output name.
     *
     * @return Error output name.
     */
    public String getErrorOutputName()
    {
        return forwarding.get("error");
    }

    /**
     * Gets currently set command.
     *
     * @return The command.
     */
    public String getCommand()
    {
        return command;
    }

    /**
     * Return a stream for output.
     *
     * @return the output stream
     */
    public IStream getOutput() throws StreamException
    {
        String name = getOutputName();
        if (name == null) return null;
        name = getAbsolutePath(name);
        IStream s = new FileStream(name);

        return s;
    }

    /**
     * Return a stream for error output.
     *
     * @return the error output stream
     */
    public IStream getErrorOutput() throws StreamException
    {
        String name = getErrorOutputName();
        if (name == null) return null;
        name = getAbsolutePath(name);
        IStream s = new FileStream(name);

        return s;
    }

    /**
     * Return a stream for input.
     *
     * @return the input stream
     */
    public IStream getInput() throws StreamException
    {
        String name = getInputName();
        if (name == null) return null;
        name = getAbsolutePath(name);
        IStream s = new FileStream(name);
        return s;
    }

    /**
     * Return command name.
     *
     * @return the command name
     */
    public String getCommandName()
    {
        return commandName;
    }

    /**
     * Return an array of arguments.
     *
     * @return the array of arguments
     */
    public String[] getArguments()
    {
        return commandArgs.toArray(new String[0]);
    }

    private String getAbsolutePath(String path)
    {
        return Global.getFileWithAbsPath(basePath, path).getAbsolutePath();
    }
}
